package leo.peersim.abcast;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Vector;

import leo.peersim.common.FileLoggerService;
import leo.peersim.common.QueueConsistencyValidator;

import org.apache.log4j.Logger;

import peersim.config.Configuration;
import peersim.core.CommonState;
import peersim.core.Control;
import peersim.core.Network;
import peersim.core.Node;

public class ABCastObserver implements Control {
	
	private Logger logger = Logger.getLogger(this.getClass().getName());
	
	private final int protocolID;

	private long minExecLatencyAllReplicas = Long.MAX_VALUE;
	private long maxExecLatencyAllReplicas = 0;
	private long avgExecLatencyAllReplicas = 0;
	
	private long minExecLatencyLeaders = Long.MAX_VALUE;
	private long maxExecLatencyLeaders = 0;
	private long avgExecLatencyLeaders = 0;

	private int numOfCompletedTransactions;
	private int numOfUncompletedTransactions;

	private int numOfCompletedClientRequests;
	
	public ABCastObserver(String prefix) {
		this.protocolID = Configuration.getPid(prefix + "." + "protocol");
	}
	
	/**
	 * 
	 */
	@Override
	public boolean execute() { 
		
		logger.info(CommonState.getTime() + "\tsimulation over... going over results...");
		
		this.logProtocolLatencies();
		
		this.logNetworkLoad();
		
		//QueueConsistencyValidator qv = new QueueConsistencyValidator(this.protocolID);	
		//int deliveryQueuesSize = qv.getQueueSize();
		
		QueueConsistencyValidator qv = new QueueConsistencyValidator(this.protocolID);
		if (qv.isConsistent()) {
			logger.debug(CommonState.getTime() + "\t"+ qv.getAtomicQueue() + " atomically broadcast on " + qv.getNumOfLiveNodes() + " agents (" + qv.getNumOfFailedNodes() + " failed)");
		} else {
			logger.fatal("Inconsistent queues :(");
		}
		
		logger.info(CommonState.getTime() + "\t" + this.numOfCompletedClientRequests + " requests executed. Leader latency [ms]: avg:" + this.avgExecLatencyLeaders + ", min:" + this.minExecLatencyLeaders + ", max:" + this.maxExecLatencyLeaders);

		return false;
	}
	
	/**
	 * 
	 * @param protocolID
	 */
	private void logProtocolLatencies() {
		
		FileLoggerService outAllTransactions = null;
		FileLoggerService outOnlyLeaders = null;
		try {
			
			outAllTransactions = new FileLoggerService(this, "all");
			outOnlyLeaders = new FileLoggerService(this, "leaders");
			
			String logString = "nodeID, transactionID, nodeIssuer, agreedTimestamp, commandIssuingTime, acceptedTime, agreedTime, executedTime, acceptedLatency, agreedLatency, executedLatency";
			String logStringLeaders = "nodeID, transactionID, nodeIssuer, commandIssuingTime, acceptedTime, agreedTime, executedTime, acceptedLatency, agreedLatency, executedLatency";
			
			outAllTransactions.log(logString);
			outOnlyLeaders.log(logStringLeaders);

			for (int i=0; i<Network.size(); i++) {				
				Node n = Network.get(i);
				ABCast abcast = (ABCast) n.getProtocol(this.protocolID);
				HashMap<String,ABCastRound> td = abcast.getManagedTransactions();
				for (String transactionID : td.keySet()) {
					
					logString = "";
					logStringLeaders = "";
					
					ABCastRound t = td.get(transactionID);
					
					if (!t.hasBeenCompleted()) {
						this.numOfUncompletedTransactions++;
						continue;
					}
					this.numOfCompletedTransactions++;
					
					String nodeID = "" + n.getID();
					String nodeIssuer = "" + t.getRequestorNode().getID();
					
					long agreedTS = t.getLamportTimestamp();
					
					long commandIssuingTime = t.getFSMCommand().getDebug_issuingTime();
					long acceptedTime = t.getDebug_acceptedTime();
					long agreedTime = t.getDebug_agreedTime();
					long executedTime = t.getFSMCommand().get_DebugExecutedTime();
					
					long agreedLatency = agreedTime - commandIssuingTime;
					long executedLatency = executedTime - commandIssuingTime;
					
					long acceptedLatency = -1;
					if (acceptedTime!=-1) {
						acceptedLatency = acceptedTime - commandIssuingTime;
						
						logStringLeaders = nodeID + ", " + transactionID + ", " + nodeIssuer + ", " + commandIssuingTime + ", " + acceptedTime + ", " + agreedTime + ", " + executedTime + ", " + acceptedLatency + ", " + agreedLatency + ", " + executedLatency;
						this.maxExecLatencyLeaders = Math.max(this.maxExecLatencyLeaders, executedLatency);
						this.minExecLatencyLeaders = Math.min(this.minExecLatencyLeaders, executedLatency);
						this.avgExecLatencyLeaders += executedLatency;
						this.numOfCompletedClientRequests += 1;
						
					}
					
					logString = nodeID + ", " + transactionID + ", " + nodeIssuer + ", " + agreedTS + ", " + commandIssuingTime + ", " + acceptedTime + ", " + agreedTime + ", " + executedTime + ", " + acceptedLatency + ", " + agreedLatency + ", " + executedLatency;
					
					if (logString.length() > 0) {
						outAllTransactions.log(logString);
					}
					
					if (logStringLeaders.length() > 0) {
						outOnlyLeaders.log(logStringLeaders);
					}
					
				}
			}
			
			if(this.numOfCompletedClientRequests!=0) {
				this.avgExecLatencyLeaders = this.avgExecLatencyLeaders / this.numOfCompletedClientRequests;
			}
			
			outAllTransactions.closeStream();
			outOnlyLeaders.closeStream();
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	/**
	 * 
	 * @param protocolID
	 */
	private void logNetworkLoad() {
		
		FileLoggerService out; 
		try {
			out = new FileLoggerService(this, "latencies");
			
			String logString = "";
			for (int i=0; i<Network.size(); i++) {
				Node n = Network.get(i);
				ABCast abcast = (ABCast) n.getProtocol(this.protocolID);
				DebugStats di = abcast.getDebufInfo();
				Vector<NetworkMessage> netMessages = di.getLatenze();
				for (NetworkMessage mex : netMessages) {
					logString = n.getID() + ", " + mex.getSentTime() + ", " + mex.getLatency() + ", " + mex.getSizeInBytes();
					
					if (logString.length() > 0) {
						out.log(logString);
					}
				}
				break; //FIXME network latencies are global to all the nodes
			}
			
			out.closeStream();
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
